package vip.liux.front.infrastructure.utils;

import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import vip.liux.contracts.security.UserDetailsInfo;

import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
import java.util.List;
import java.util.UUID;

import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static vip.liux.front.infrastructure.utils.JwtConverter.ClaimNames.*;
import static vip.liux.front.infrastructure.utils.KeyGenerator.getPrivateKey;
import static vip.liux.front.infrastructure.utils.KeyGenerator.getPublicKey;

public class JwtConverter {

    public static final String ISSUER = "issuer";
    public static final String AUDIENCE = "audience";
    public static final long EXPIRY = 36000L;
    public final static JwtDecoder jwtDecoder = jwtDecoder();
    public final static JwtEncoder jwtEncoder = jwtEncoder();
    private static final Logger log = LoggerFactory.getLogger(JwtConverter.class);

    public static TokenModel generateToken(UserDetailsInfo userDetail) {
        Instant now = Instant.now();
        // @formatter:off
        JwtClaimsSet claims = JwtClaimsSet.builder()
                .issuer(ISSUER) // iss 签发者
                .subject(userDetail.getUsername()) // sub 主题
                .audience(List.of(AUDIENCE)) // aud 接收者
                .expiresAt(now.plusSeconds(EXPIRY)) // exp 过期时间
                .notBefore(now) // nbf 生效时间
                .issuedAt(now) // iat 签发时间
                .id(UUID.randomUUID().toString()) // jti 唯一标识
                .claim(ROLE, AuthorityUtils.authorityListToSet(userDetail.getAuthorities()))
                .claim(USER_NAME, userDetail.getUsername())
                .claim(NAME,defaultIfBlank( userDetail.getName(), EMPTY))
                .claim(SUR_NAME, defaultIfBlank(userDetail.getSurName(), EMPTY))
                .claim(USER_ID, userDetail.getId())
                .claim(EMAIL, userDetail.getEmail())
                .claim(EMAIL_VERIFIED, userDetail.isEmailConfirmed())
                .claim(PHONE_NUMBER, userDetail.getPhoneNumber())
                .claim(PHONE_NUMBER_VERIFIED, userDetail.isPhoneNumberConfirmed())
                .build();

        // @formatter:on
        Jwt encode = jwtEncoder.encode(JwtEncoderParameters.from(claims));
        // Serialize to compact form
        return new TokenModel(encode.getTokenValue(), EXPIRY);
    }

    public static JwtClaimsSet verifyToken(String token) {
        Jwt jwt;
        try {
            jwt = jwtDecoder.decode(token);
        } catch (BadJwtException failed) {
            log.debug("Failed to authenticate since the JWT was invalid");
            throw new InvalidBearerTokenException(failed.getMessage(), failed);
        } catch (JwtException failed) {
            throw new AuthenticationServiceException(failed.getMessage(), failed);
        }
        return JwtClaimsSet.builder()
                .claims(claims -> claims.putAll(jwt.getClaims()))
                .build();
    }

    static JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey((RSAPublicKey) getPublicKey()).build();
    }

    static JwtEncoder jwtEncoder() {
        JWK jwk = new RSAKey.Builder((RSAPublicKey) getPublicKey()).privateKey(getPrivateKey()).build();
        JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwkSource);
    }

    public interface ClaimNames {
        String USER_NAME = "name";
        String NAME = "givenname";
        String SUR_NAME = "surname";
        String USER_ID = "nameidentifier";
        String ROLE = "role";
        String EMAIL = "emailaddress";
        String EMAIL_VERIFIED = "email_verified";
        String PHONE_NUMBER = "phone_number";
        String PHONE_NUMBER_VERIFIED = "phone_number_verified";
    }

    public record TokenModel(String accessToken, long expiresIn) {
    }
}
